/* AcessOS
 * FIFO Pipe Driver
 */
#include <acess.h>
#include <fs_devfs2.h>

// === CONSTANTS ===
#define DEFAULT_RING_SIZE	2048
#define PF_BLOCKING		1

// === TYPES ===
typedef struct sPipe {
	struct sPipe	*Next;
	vfs_node	Node;
	Uint	Flags;
	 int	ReadPos;
	 int	WritePos;
	 int	BufSize;
	char	*Buffer;
} tPipe;

// === PROTOTYPES ===
 int	FIFO_Install(char *Options);
 int	FIFO_IOCtl(vfs_node *Node, int Id, void *Data);
vfs_node	*FIFO_ReadDir(vfs_node *Node, int Id);
vfs_node	*FIFO_FindDir(vfs_node *Node, char *Filename);
vfs_node	*FIFO_MkNod(vfs_node *Node, char *Name, int Flags);
 int	FIFO_Close(vfs_node *Node);
 int	FIFO_Relink(vfs_node *Node, char *Newname);
 int	FIFO_Read(vfs_node *Node, Uint64 Offset, Uint64 Length, void *Buffer);
 int	FIFO_Write(vfs_node *Node, Uint64 Offset, Uint64 Length, void *Buffer);
tPipe	*FIFO_Int_NewPipe(int Size, char *Name);

// === GLOBALS ===
devfs_driver	gFIFO_DriverInfo = {
	{
		.name = "fifo", .nameLength = 4,
		.mode = 0111,
		.flags = VFS_FFLAG_DIRECTORY,
		.readdir = FIFO_ReadDir,
		.finddir = FIFO_FindDir,
		.mknod = FIFO_MkNod
	},
	FIFO_IOCtl
	};
vfs_node	gFIFO_AnonNode = {
	.name = "anon", .nameLength = 4,
	.mode = 0666
	};
tPipe	*gFIFO_NamedPipes = NULL;

// === INSTALL ===
DEFMOD(FIFO, FIFO_Install);

// === CODE ===
/**
 * \fn int FIFO_Install(char *Options)
 * \brief Installs the FIFO Driver
 */
int FIFO_Install(char *Options)
{
	gFIFO_DriverInfo.rootNode.impl = dev_addDevice( &gFIFO_DriverInfo );
	return 0;
}

/**
 * \fn int FIFO_IOCtl(vfs_node *Node, int Id, void *Data)
 */
int FIFO_IOCtl(vfs_node *Node, int Id, void *Data)
{
	return 0;
}

/**
 * \fn vfs_node *FIFO_ReadDir(vfs_node *Node, int Id)
 * \brief Reads from the FIFO root
 */
vfs_node *FIFO_ReadDir(vfs_node *Node, int Id)
{
	tPipe	*tmp = gFIFO_NamedPipes;
	// Entry 0 is Anon Pipes
	if(Id == 0)	return &gFIFO_AnonNode;
	
	// Find the id'th node
	while(--Id && tmp)	tmp = tmp->Next;
	// If node found, return it
	if(tmp)	return &tmp->Node;
	// else error return
	return NULL;
}

/**
 * \fn vfs_node *FIFO_FindDir(vfs_node *Node, char *Filename)
 * \brief Find a file in the FIFO root
 * \note Creates an anon pipe if anon is requested
 */
vfs_node *FIFO_FindDir(vfs_node *Node, char *Filename)
{
	tPipe	*tmp;
	if(!Filename)	return NULL;
	
	// NULL String Check
	if(Filename[0] == '\0')	return NULL;
	
	// Anon Pipe
	if(Filename[0] == 'a' && Filename[1] == 'n'
	&& Filename[2] == 'o' && Filename[3] == 'n'
	&& Filename[4] == '\0') {
		tmp = FIFO_Int_NewPipe(DEFAULT_RING_SIZE, gFIFO_AnonNode.name);
		return &tmp->Node;
	}
	
	// Check Named List
	tmp = gFIFO_NamedPipes;
	while(tmp)
	{
		if(strcmp(tmp->Node.name, Filename) == 0)
			return &tmp->Node;
		tmp = tmp->Next;
	}
	return NULL;
}

/**
 * \fn vfs_node *FIFO_MkNod(vfs_node *Node, char *Name, int Flags)
 */
vfs_node *FIFO_MkNod(vfs_node *Node, char *Name, int Flags)
{
	return NULL;
}

/**
 * \fn int FIFO_Close(vfs_node *Node)
 */
int FIFO_Close(vfs_node *Node)
{
	if(!Node->ImplPtr)	return 1;
	
	if(Node->name == gFIFO_AnonNode.name) {
		free(Node->ImplPtr);
		return 1;
	}
	
	return 0;
}

/**
 * \fn int FIFO_Relink(vfs_node *Node, char *Newname)
 * \brief Relink a file (Deletes named pipes)
 */
int FIFO_Relink(vfs_node *Node, char *Newname)
{
	// Can't Unlink/Relink Root
	if(Node == &gFIFO_DriverInfo.rootNode)	return 0;
	// Same for anon pipes
	if(Node->name == gFIFO_AnonNode.name)	return 0;
	
	// Relink a named pipe
	if(Newname) {
		Node->nameLength = strlen(Newname);
		Node->name = malloc(Node->nameLength+1);
		strcpy(Node->name, Newname);
		return 1;
	}
	
	// Unlink the pipe
	if(Node->ImplPtr) {
		free(Node->ImplPtr);
		return 1;
		
	}
	
	return 0;
}

/**
 * \fn int FIFO_Read(vfs_node *Node, Uint64 Offset, Uint64 Length, void *Buffer)
 * \brief Read from a fifo pipe
 */
int FIFO_Read(vfs_node *Node, Uint64 Offset, Uint64 Length, void *Buffer)
{
	tPipe	*pipe = Node->ImplPtr;
	Uint	len;
	Uint	remaining = Length;
	
	if(!pipe)	return 0;
	
	while(remaining)
	{
		// Wait for buffer to fill
		if(pipe->Flags & PF_BLOCKING)
			while(pipe->ReadPos == pipe->WritePos)
				Proc_Yield();
		else
			if(pipe->ReadPos == pipe->WritePos)
				return 0;
		
		// Read buffer
		if(pipe->WritePos - pipe->ReadPos < remaining)
			len = pipe->WritePos - pipe->ReadPos;
		else
			len = remaining;
		
		// Check if read overflows buffer
		if(len > pipe->BufSize - pipe->ReadPos)
		{
			int ofs = pipe->BufSize - pipe->ReadPos;
			memcpy(Buffer, &pipe->Buffer[pipe->ReadPos], ofs);
			memcpy(Buffer + ofs, &pipe->Buffer, len-ofs);
		}
		else
		{
			memcpy(Buffer, &pipe->Buffer[pipe->ReadPos], len);
		}
		
		// Increment read position
		pipe->ReadPos += len;
		pipe->ReadPos %= pipe->BufSize;
		
		// Decrement Remaining Bytes
		remaining -= len;
		// Increment Buffer address
		Buffer += len;
	}

	return Length;

}

/**
 * \fn int FIFO_Write(vfs_node *Node, Uint64 Offset, Uint64 Length, void *Buffer)
 * \brief Write to a fifo pipe
 */
int FIFO_Write(vfs_node *Node, Uint64 Offset, Uint64 Length, void *Buffer)
{
	tPipe	*pipe = Node->ImplPtr;
	Uint	len;
	Uint	remaining = Length;
	
	if(!pipe)	return 0;
	
	while(remaining)
	{
		// Wait for buffer to empty
		if(pipe->Flags & PF_BLOCKING)
			while(pipe->ReadPos == (pipe->WritePos+1)%pipe->BufSize)
				Proc_Yield();
		else
			if(pipe->ReadPos == (pipe->WritePos+1)%pipe->BufSize)
				return 0;
		
		// Write buffer
		if(pipe->ReadPos - pipe->WritePos < remaining)
			len = pipe->ReadPos - pipe->WritePos;
		else
			len = remaining;
		
		// Check if write overflows buffer
		if(len > pipe->BufSize - pipe->WritePos)
		{
			int ofs = pipe->BufSize - pipe->WritePos;
			memcpy(&pipe->Buffer[pipe->WritePos], Buffer, ofs);
			memcpy(&pipe->Buffer, Buffer + ofs, len-ofs);
		}
		else
		{
			memcpy(&pipe->Buffer[pipe->WritePos], Buffer, len);
		}
		
		// Increment read position
		pipe->WritePos += len;
		pipe->WritePos %= pipe->BufSize;
		
		// Decrement Remaining Bytes
		remaining -= len;
		// Increment Buffer address
		Buffer += len;
	}

	return Length;
}

// --- HELPERS ---
/**
 * \fn tPipe *FIFO_Int_NewPipe(int Size, char *Name)
 * \brief Create a new pipe
 */
tPipe *FIFO_Int_NewPipe(int Size, char *Name)
{
	tPipe	*ret;
	
	ret = malloc(sizeof(tPipe));
	if(!ret)	return NULL;
	
	// Clear Return
	memsetd(ret, 0, sizeof(tPipe)/4);
	
	// Allocate Buffer
	ret->BufSize = Size;
	ret->Buffer = malloc(Size);
	if(!ret->Buffer) {
		free(ret);
		return NULL;
	}
	
	// Set Node
	ret->Node.name = Name;
	ret->Node.nameLength = strlen(Name);
	ret->Node.length = 0;
	ret->Node.ImplPtr = ret;
	ret->Node.uid = Proc_GetUid();
	ret->Node.gid = Proc_GetGid();
	ret->Node.mode = 0600;
	ret->Node.ctime = ret->Node.mtime = ret->Node.atime = unow();
	ret->Node.read = FIFO_Read;
	ret->Node.write = FIFO_Write;
	ret->Node.close = FIFO_Close;
	ret->Node.relink = FIFO_Relink;
	
	return ret;
}
